/*Copyright (C) 2014  JD Software, Inc.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU Affero General Public License as
  published by the Free Software Foundation, either version 3 of the
  License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.jd.survey.web.settings;

import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.WebUtils;

import com.jd.survey.domain.security.User;
import com.jd.survey.domain.settings.Invitation;
import com.jd.survey.domain.settings.SurveyDefinition;
import com.jd.survey.service.security.SecurityService;
import com.jd.survey.service.security.UserService;
import com.jd.survey.service.settings.SurveySettingsService;

import au.com.bytecode.opencsv.CSVReader;

@RequestMapping("/settings/invitations")
@Controller
public class InvitationController {
    private static final Log log = LogFactory.getLog(InvitationController.class);
    @Autowired
    private MessageSource messageSource;
    @Autowired
    private UserService userService;
    @Autowired
    private SurveySettingsService surveySettingsService;
    @Autowired
    private SecurityService securityService;

    private static final String FIRST_NAME_MESSAGE = "com.jd.survey.domain.settings.invitation.firstname_label";
    private static final String MIDDLE_NAME_MESSAGE = "com.jd.survey.domain.settings.invitation.middlename_label";
    private static final String LAST_NAME_MESSAGE = "com.jd.survey.domain.settings.invitation.lastname_label";
    private static final String EMAIL_MESSAGE = "com.jd.survey.domain.settings.invitation.email_label";

    private static final String INVITATION_EMAIL_TITLE = "invitation_email_title";

    private static final String INVITEE_FULLNAME_PARAMETER_NAME = "invitee_fullname_parameter_name";
    private static final String SURVEY_NAME = "survey_name";

    private static final String INVITE_FILL_SURVEY_LINK_PARAMETER_NAME = "invite_fill_survey_link_parameter_name";
    private static final String INVITE_FILL_SURVEY_LINK_LABEL = "invite_fill_survey_link_label";

    // private static final String INTERNAL_SITE_BASE_URL="internal_site_base_url";
    // private static final String EXTERNAL_SITE_BASE_URL="external_site_base_url";
    @Value("${external.base.url}")
    String externalBaseUrl;
    @Value("${internal.base.url}")
    String internalBaseUrl;

    /**
     *  exports a sample invitations comma delimited file
     * @param dataSetId
     * @param principal
     * @param response
     */
    @Secured({ "ROLE_ADMIN", "ROLE_SURVEY_ADMIN" })
    @RequestMapping(value = "/example", produces = "text/html")
    public void getExampleCsvFile(Principal principal, HttpServletResponse response) {
        try {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(messageSource
                    .getMessage(FIRST_NAME_MESSAGE, null, LocaleContextHolder.getLocale()).replace(",", ""));
            stringBuilder.append(",");
            stringBuilder.append(messageSource
                    .getMessage(MIDDLE_NAME_MESSAGE, null, LocaleContextHolder.getLocale()).replace(",", ""));
            stringBuilder.append(",");
            stringBuilder.append(messageSource
                    .getMessage(LAST_NAME_MESSAGE, null, LocaleContextHolder.getLocale()).replace(",", ""));
            stringBuilder.append(",");
            stringBuilder.append(messageSource.getMessage(EMAIL_MESSAGE, null, LocaleContextHolder.getLocale())
                    .replace(",", ""));
            stringBuilder.append("\n");
            stringBuilder.append("a,b,c,abc@jdsoft.com\n");
            // response.setContentType("text/html; charset=utf-8");
            response.setContentType("application/octet-stream");
            // Set standard HTTP/1.1 no-cache headers.
            response.setHeader("Cache-Control", "no-store, no-cache,must-revalidate");
            // Set IE extended HTTP/1.1 no-cache headers (use addHeader).
            response.addHeader("Cache-Control", "post-check=0, pre-check=0");
            // Set standard HTTP/1.0 no-cache header.
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Content-Disposition", "inline;filename=Invitationsexample.csv");
            ServletOutputStream servletOutputStream = response.getOutputStream();
            servletOutputStream.write(stringBuilder.toString().getBytes("UTF-8"));
            servletOutputStream.flush();

        }

        catch (Exception e) {
            log.error(e.getMessage(), e);
            throw (new RuntimeException(e));
        }
    }

    /**
     * Shows a list of completed Survey Definitions for the user 
     * @param surveyId
     * @param principal
     * @param uiModel
     * @param httpServletRequest
     * @return
     */
    @Secured({ "ROLE_ADMIN", "ROLE_SURVEY_ADMIN" })
    @RequestMapping(produces = "text/html", method = RequestMethod.GET)
    public String listSurveys(Model uiModel, Principal principal) {

        try {
            User user = userService.user_findByLogin(principal.getName());
            Set<SurveyDefinition> surveyDefinitions = surveySettingsService
                    .surveyDefinition_findAllCompletedInternal(user);
            uiModel.addAttribute("surveyDefinitions", surveyDefinitions);
            return "settings/invitations/invitations";
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw (new RuntimeException(e));
        }
    }

    /**
     * Shows the list of Survey Invitations for a Survey Definition, Supports Paging 
     * @param surveyId
     * @param principal
     * @param uiModel
     * @param httpServletRequest
     * @return
     */
    @Secured({ "ROLE_ADMIN", "ROLE_SURVEY_ADMIN" })
    @RequestMapping(value = "/list", produces = "text/html", method = RequestMethod.GET)
    public String listSurveyInvitations(@RequestParam(
            value = "id", required = true
            ) Long surveyDefinitionId, @RequestParam(
                    value = "page", required = false
                    ) Integer page,
            @RequestParam(value = "size", required = false) Integer size, @RequestParam(value = "fileContentError", required = false) boolean contentError, Model uiModel, Principal principal, HttpServletRequest httpServletRequest) {

        try {
            User user = userService.user_findByLogin(principal.getName());

            if (!securityService.userIsAuthorizedToManageSurvey(surveyDefinitionId, user)) {
                log.warn("Unauthorized access to url path " + httpServletRequest.getPathInfo()
                        + " attempted by user login:" + principal.getName() + "from IP:"
                        + httpServletRequest.getLocalAddr());
                return "accessDenied";
            }
            Set<SurveyDefinition> surveyDefinitions = surveySettingsService
                    .surveyDefinition_findAllCompletedInternal(user);
            uiModel.addAttribute("surveyDefinitions", surveyDefinitions);
            uiModel.addAttribute("surveyDefinition",
                    surveySettingsService.surveyDefinition_findById(surveyDefinitionId));
            Long surveyInvitationsCount = surveySettingsService.invitation_getSurveyCount(surveyDefinitionId);
            uiModel.addAttribute("surveyInvitationsCount", surveyInvitationsCount);
            uiModel.addAttribute("surveyInvitationsOpenedCount",
                    surveySettingsService.invitation_getSurveyOpenedCount(surveyDefinitionId));

            int sizeNo = size == null ? 25 : size.intValue();
            final int firstResult = page == null ? 0 : (page.intValue() - 1) * sizeNo;
            Set<Invitation> invitations = surveySettingsService.invitation_findSurveyAll(surveyDefinitionId,
                    firstResult, sizeNo);
            float nrOfPages = (float) surveyInvitationsCount / sizeNo;
            int maxPages = (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1
                    : nrOfPages);
            uiModel.addAttribute("maxPages", maxPages);
            uiModel.addAttribute("invitations", invitations);
            uiModel.addAttribute("fileContentError", contentError);// Used to carry error from
                                                                   // file upload over and make
                                                                   // it available to the view
            return "settings/invitations/invitations";
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw (new RuntimeException(e));
        }
    }

    /**
     * prepares the page to import invitations from a csv file		 
     * @param dataSetId
     * @param principal
     * @param uiModel
     * @param httpServletRequest
     * @return
     */
    @Secured({ "ROLE_ADMIN", "ROLE_SURVEY_ADMIN" })
    @RequestMapping(value = "/import", produces = "text/html")
    public String prepareForImport(@RequestParam(value = "id", required = false) Long surveyDefinitionId,
            Principal principal, Model uiModel, HttpServletRequest httpServletRequest) {
        try {
            User user = userService.user_findByLogin(principal.getName());
            Set<SurveyDefinition> surveyDefinitions = surveySettingsService
                    .surveyDefinition_findAllCompletedInternal(user);
            uiModel.addAttribute("surveyDefinitions", surveyDefinitions);
            if (surveyDefinitionId != null) {
                if (!securityService.userIsAuthorizedToManageSurvey(surveyDefinitionId, user)) {
                    log.warn("Unauthorized access to url path " + httpServletRequest.getPathInfo()
                            + " attempted by user login:" + principal.getName() + "from IP:"
                            + httpServletRequest.getLocalAddr());
                    return "accessDenied";
                }
                uiModel.addAttribute("surveyDefinition",
                        surveySettingsService.surveyDefinition_findById(surveyDefinitionId));
            }
            return "settings/invitations/upload";
        }

        catch (Exception e) {
            log.error(e.getMessage(), e);
            throw (new RuntimeException(e));
        }
    }

    /**
     * Sends email invitations to the list of invitees from csv file.  
     * @param file
     * @param surveyDefinitionId
     * @param proceed
     * @param principal
     * @param uiModel
     * @param httpServletRequest
     * @return
     */
    @SuppressWarnings("unchecked")
    @Secured({ "ROLE_ADMIN", "ROLE_SURVEY_ADMIN" })
    @RequestMapping(value = "/upload", method = RequestMethod.POST, produces = "text/html")
    public String sendInvitations(@RequestParam(
        "file") MultipartFile file, @RequestParam(
        "id") Long surveyDefinitionId, @RequestParam(value = "_proceed", required = false) String proceed,
            Principal principal, Model uiModel, HttpServletRequest httpServletRequest) {
        try {
            short firstNameFieldIndex = 0;
            short middleNameFieldIndex = 1;
            short lastNameFieldIndex = 2;
            short emailNameFieldIndex = 3;

            if (proceed != null) {
                // prepare the base url links
                String emailSubject = messageSource.getMessage(INVITATION_EMAIL_TITLE, null,
                        LocaleContextHolder.getLocale());
                // String surveyLink =messageSource.getMessage(EXTERNAL_SITE_BASE_URL, null,
                // LocaleContextHolder.getLocale());
                String surveyLink = externalBaseUrl;
                // String trackingImageLink =messageSource.getMessage(INTERNAL_SITE_BASE_URL,
                // null, LocaleContextHolder.getLocale());
                String trackingImageLink = internalBaseUrl;

                SurveyDefinition surveyDefinition = surveySettingsService
                        .surveyDefinition_findById(surveyDefinitionId);

                User user = userService.user_findByLogin(principal.getName());
                Set<SurveyDefinition> surveyDefinitions = surveySettingsService
                        .surveyDefinition_findAllCompletedInternal(user);
                // Check if the user is authorized
                if (!securityService.userIsAuthorizedToManageSurvey(surveyDefinitionId, user)) {
                    log.warn("Unauthorized access to url path " + httpServletRequest.getPathInfo()
                            + " attempted by user login:" + principal.getName() + "from IP:"
                            + httpServletRequest.getLocalAddr());
                    return "accessDenied";
                }

                if (trackingImageLink.endsWith("/")) {
                    trackingImageLink = trackingImageLink + "public/w/";
                } else {
                    trackingImageLink = trackingImageLink + "/public/w/";
                }

                if (surveyDefinition.getIsPublic()) {
                    if (surveyLink.endsWith("/")) {
                        surveyLink = surveyLink + "open/";
                    } else {
                        surveyLink = surveyLink + "/open/";
                    }
                } else {
                    if (surveyLink.endsWith("/")) {
                        surveyLink = surveyLink + "private/";
                    } else {
                        surveyLink = surveyLink + "/private/";
                    }
                }

                String emailContent;

                if (!file.isEmpty() && ((file.getContentType().equalsIgnoreCase("application/vnd.ms-excel"))
                        || (file.getContentType().equalsIgnoreCase("text/csv"))
                        || (file.getContentType().equalsIgnoreCase("text/plain")))) {
                    CSVReader csvReader = new CSVReader(new InputStreamReader(file.getInputStream()));
                    String[] nextLine;
                    nextLine = csvReader.readNext(); // skip the first row the continue on with
                                                     // loop

                    while ((nextLine = csvReader.readNext()) != null) {
                        emailContent = "";
                        StringWriter sw = new StringWriter();

                        // Prevents IndexOutOfBoundException by skipping line if there are not
                        // enough elements in the array nextLine.
                        if (nextLine.length < 4) {
                            // Continues if a blank line is present without setting an error
                            // message (if a new line character is present CSVReader will return
                            // and array with one empty string at index 0).
                            if (nextLine.length == 1 && nextLine[0].isEmpty()) {
                                continue;
                            }
                            uiModel.addAttribute("fileContentError", true);
                            continue;
                        }
                        // Prevents exception from attempting to send an email with an empty
                        // string for an email address.
                        if (nextLine[3].isEmpty()) {
                            uiModel.addAttribute("fileContentError", true);
                            continue;
                        }
                        // creating the Invitation
                        Invitation invitation = new Invitation(nextLine[firstNameFieldIndex].trim(),
                                nextLine[middleNameFieldIndex].trim(), nextLine[lastNameFieldIndex].trim(),
                                nextLine[emailNameFieldIndex].trim(), surveyDefinition);

                        Map model = new HashMap();
                        // survey name
                        model.put(messageSource.getMessage(SURVEY_NAME, null, LocaleContextHolder.getLocale())
                                .replace("${", "").replace("}", ""), surveyDefinition.getName());
                        // full name
                        model.put(
                                messageSource
                                        .getMessage(INVITEE_FULLNAME_PARAMETER_NAME, null,
                                                LocaleContextHolder.getLocale())
                                        .replace("${", "").replace("}", ""),
                                invitation.getFullName());
                        // survey link
                        model.put(
                                messageSource
                                        .getMessage(INVITE_FILL_SURVEY_LINK_PARAMETER_NAME, null,
                                                LocaleContextHolder.getLocale())
                                        .replace("${", "").replace("}", ""),
                                "<a href='" + surveyLink + surveyDefinition.getId() + "?list'>"
                                        + messageSource.getMessage(INVITE_FILL_SURVEY_LINK_LABEL, null,
                                                LocaleContextHolder.getLocale())
                                        + "</a>");

                        VelocityContext velocityContext = new VelocityContext(model);
                        Velocity.evaluate(velocityContext, sw, "velocity-log",
                                surveyDefinition.getEmailInvitationTemplate());
                        emailContent = sw.toString().trim();

                        if (emailContent.length() > 14 && emailContent.substring(emailContent.length() - 14)
                                .toUpperCase().equalsIgnoreCase("</BODY></HTML>")) {
                            emailContent = emailContent.substring(0, emailContent.length() - 14) + "<img src='"
                                    + trackingImageLink + invitation.getUuid() + "'/></BODY></HTML>";
                            emailContent = "<BODY><HTML>" + emailContent;
                        } else {
                            // template is incorrect or not html do not include tracking white
                            // gif
                            emailContent = emailContent + "<img src='" + trackingImageLink
                                    + invitation.getUuid() + "'/></BODY></HTML>";

                        }

                        surveySettingsService.invitation_send(invitation, emailSubject, emailContent);
                    }

                    return "redirect:/settings/invitations/list?id="
                            + encodeUrlPathSegment(surveyDefinitionId.toString(), httpServletRequest);
                } else {
                    uiModel.addAttribute("surveyDefinitions", surveyDefinitions);
                    uiModel.addAttribute("surveyDefinition", surveyDefinition);
                    uiModel.addAttribute("emptyFileError", true);
                    return "settings/invitations/upload";
                }
            } else {
                return "redirect:/settings/invitations/list?id="
                        + encodeUrlPathSegment(surveyDefinitionId.toString(), httpServletRequest);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    String encodeUrlPathSegment(String pathSegment, HttpServletRequest httpServletRequest) {
        log.info("encodeUrlPathSegment()");
        try {
            String enc = httpServletRequest.getCharacterEncoding();
            if (enc == null) {
                enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
            }
            try {
                pathSegment = UriUtils.encodePathSegment(pathSegment, enc);
            } catch (UnsupportedEncodingException uee) {
                log.error(uee);
            }
            return pathSegment;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw (new RuntimeException(e));
        }
    }

    @ExceptionHandler(RuntimeException.class)
    public String handleRuntimeException(RuntimeException ex, HttpServletRequest request) {
        log.error(ex);
        log.error("redirect to /uncaughtException");
        return "redirect:/uncaughtException";
    }

}
